<?php

namespace App\Http\Controllers\Reservation;

use App\Http\Controllers\ApiController;
use App\Http\Requests\Reservation\ReservationStoreRequest;
use App\Models\Reservation\Reservation;
use App\Models\Reservation\Schedule;
use App\Notifications\Reservation\CancelReservationToDoctor;
use App\Notifications\Reservation\CancelReservationToNurse;
use App\Notifications\Reservation\CancelReservationToPatient;
use App\Notifications\Reservation\HasPatientIntoPool;
use App\Notifications\Reservation\ReservePatientSms;
use App\Notifications\Reservation\SuccessReservationToDoctor;
use App\Project\Reserve\Pool;
use App\Project\Reserve\Reserve;
use App\Project\Traits\SearchAble;
use App\Transformers\Reservation\ReservationTransformer;
use App\User;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Http\Request;

class ReservationController extends ApiController
{
    use SearchAble;

    protected $patientQueryMap = [
        'name'  => ['field' => 'tn_user_base.name', 'operator' => 'like'],
        'phone' => ['field' => 'tn_user_base.phone', 'operator' => '='],
    ];

    protected $reserveQueryMap = [
        'am'     => ['field' => 'st_reservations.am', 'operator' => '='],
        'status' => ['field' => 'st_reservations.status', 'operator' => '='],
    ];

    public function index(Request $request)
    {
        $input = $request->except('doctor_id');

        $reserve = (new Reserve(\Auth::user()))->look();

        if (isset($input['times'][0])) {
            $start             = Carbon::parse($input['times'][0]);
            $now               = Carbon::now();
            $input['times'][0] = $start->gt($now) ? $start : $now;
            $input['times'][0] = $input['times'][0]->toDateString();
        }
        $paginate = $reserve->forUser($request->input('doctor_id'))->wherePatient(
            $this->parseSearchQueryToWhere($input, $this->patientQueryMap)
        )->whereReserve(
            $this->parseSearchQueryToWhere($input, $this->reserveQueryMap)
        )->whereTimeBetween($input['times'])
                            ->orderBy('time', 'desc')
                            ->with('doctor')->paginate();
        return $this->paginate($paginate, (new ReservationTransformer())->setDefaultIncludes(['patient', 'doctor']));
    }

    /**
     * @param \App\Http\Requests\Reservation\ReservationStoreRequest $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function store(ReservationStoreRequest $request)
    {
        $input = $request->input();

        if (\Auth::id() == $input['doctor_id']) {
            $doctor = \Auth::user();
        } else {
            if (\Auth::user()->isA('doctor')) {
                abort(401, '暂时没有为其他医生添加预约的权限。');
            }
            $doctor = User::simpleField()->find($input['doctor_id']);
        }

        $reservation = new Reserve($doctor);

        $new = $reservation->launch()->to($input['patient_id'], $input);

        if (is_bool($new)) {
            return $this->hasNoData([], '预约已经创建过了。', 40007);
        }

        return $this->item($new, new ReservationTransformer());
    }

    /**
     * 发送预约短信通知
     * @param \Illuminate\Http\Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function notify(Request $request)
    {
        $ids = $request->input('ids');

        $reservations = Reservation::where('status', 1)->find($ids);

        if ($reservations->count() == 0) {
            abort(422, '您选择的预约已经发送过短信了。');
        }

        $reservations->load('patient');

        //  通知患者
        $reservations->map(function (Reservation $reservation) {
            $reservation->patient->notify(new ReservePatientSms($reservation));
        });

        //  医生消息
        \Auth::user()->notify(new SuccessReservationToDoctor($reservations));

        return $this->response($reservations->pluck('id')->toArray());
    }

    /**
     * Display the specified resource.
     * @param $id
     * @return \Illuminate\Http\JsonResponse
     */
    public function edit($id)
    {
        return $this->item(Reservation::findOrFail($id), new ReservationTransformer());
    }

    /**
     * @param \Illuminate\Http\Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function update(Request $request)
    {
        $this->idsValidator($request);

        $ids = $request->input('ids');

        $input = $request->except('ids');

        $result = Reservation::whereIn('id', $ids)->update($input);

        return $this->responseForUpdate($result);
    }

    /**
     * 取消预约(取消后，患者进入患者池)
     * @param \Illuminate\Http\Request $request
     * @return \Illuminate\Http\JsonResponse
     * @throws \Exception
     * @throws \Throwable
     */
    public function destroy(Request $request)
    {
        $this->idsValidator($request);

        $ids = $request->input('ids');

        $result = Reservation::select(['id', 'status'])->whereIn('id', $ids)->where('status', 2)->get();

        if ($result->count() === 0) {
            return $this->setMessage('没有可以删除的项目。')->setCode(40003)->response();
        }

        if ($result->count() !== count($ids)) {
            return $this->setMessage('参数中包含不可以删除的项目。')->setCode(40003)->response();
        }

        $delete = \DB::transaction(function () use ($result, $ids) {
            $delete = Reservation::whereIn('id', $result->pluck('id')->toArray())->delete();

            $pool = new Pool();
            $pool->createById($ids);

            return $delete;
        });

        $this->afterDeleteNotification($ids);

        return $this->responseForDelete($delete);
    }

    /**
     * 预约取消后，给患者和医生发送消息
     * @param $reservation_ids
     */
    protected function afterDeleteNotification($reservation_ids)
    {
        $reservations = Reservation::with([
            'patient'     => function (BelongsTo $belongsTo) {
                $belongsTo->select(['id', 'name', 'phone']);
            }, 'hospital' => function (BelongsTo $belongsTo) {
                $belongsTo->select(['id', 'name']);
            }, 'doctor'
        ])->onlyTrashed()->whereIn('id', $reservation_ids)->get();

        if ($reservations->count() > 0) {

            //  给预约涉及到的患者发送短信
            $reservations->map(function (Reservation $reservation) {
                $reservation->patient->notify(new CancelReservationToPatient($reservation));
            });

            //  记录医生取消预约的消息
            \Auth::user()->notify(new CancelReservationToDoctor($reservations));

            //  通知其他医生有预约的患者了
            $times = $reservations->pluck('time')->toArray();   //  取消的预约原本的预约时间

            //  根据预约时间，获取这个时间下有安排的医生ID
            $doctor_ids = Schedule::distinct()->select('doctor_id')
                                  ->whereHospital(\Auth::user()->hospital_id)
                                  ->where('doctor_id', '<>', \Auth::id())
                                  ->whereIn('time', $times)
                                  ->get()->pluck('doctor_id');

            $doctor_ids->map(function ($id) use ($reservations) {
                (new User())->forceFill(['id' => $id])->notify(new HasPatientIntoPool($reservations));
            });

            //  给管理员发信息
            $nurses = User::where([
                'hospital_id' => \Auth::user()->hospital_id,
                'type'        => 3,
                ['id', '<>', \Auth::id()]
            ])->select('id')->get();

            $nurses->map(function (User $nurse) use ($reservations) {
                $nurse->notify(new CancelReservationToNurse($reservations, \Auth::user()));
            });
        }
    }

    protected function idsValidator(Request $request)
    {
        $this->validate(
            $request,
            [
                'ids' => 'required|array|exists:st_reservations,id',
            ]
        );
    }
}
